package tiled.core;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.imageio.ImageIO;

import tiled.mapeditor.util.TransparentImageFilter;
import tiled.mapeditor.util.cutter.BasicTileCutter;
import tiled.mapeditor.util.cutter.TileCutter;
import tiled.util.NumberedSet;

public class TileSet
{
    private String base;
    private NumberedSet tiles, images;
    private int firstGid;
    private long tilebmpFileLastModified;
    private TileCutter tileCutter;
    private Rectangle tileDimensions;
    private int tileSpacing;
    private int tileMargin;
    private int tilesPerRow;
    private String externalSource;
    private File tilebmpFile;
    private String name;
    private Color transparentColor;
    private Properties defaultTileProperties;
    private Image tileSetImage;
    private LinkedList<TilesetChangeListener> tilesetChangeListeners;

    public TileSet()
    {
        tiles = new NumeredSet();
        images = new NumeredSet();
        tileDimensions = new Rectangle();
        defaultTileProperties = new Properties();
        tilesetChangeListeners = new LinkedList();
    }

    public void importTileBitmap(String imgFilename, TileCutter cutter)
            throws IOException
    {
        setTilesetImageFilename(imgFilename);

        Image image = ImageIO.read(new File(imgFilename));
        if (image == null)
        {
            throw new IOException("Failed to load " + tilebmpFile);
        }

        Toolkit tk = Toolkit.getDefaultToolkit();

        if (transparentColor != null)
        {
            int rgb = transparentColor.getRGB();
            image = tk.createImage(
                    new FilteredImageSource(image.getSource(),
                        new TransparentImageFilter(rgb)));
        }

        BufferedImage buffered = new BufferedImage(
                image.getWidth(null),
                image.getHeight(null),
                BufferedImage.TYPE_INT_ARGB);
        buffered.getGraphics().drawImage(image, 0, 0, null);

        importTileBitmap(buffered, cutter);
    }

    private void importTileBitmap(BufferedImage tilebmp, TileCutter cutter)
    {
        assert tilebmp != null;
        assert cutter != null;

        tileCutter = cutter;
        tileSetImage = tilebmp;

        cutter.setImage(tilebmp);

        tileDimensions = new Rectangle(cutter.getTileDimensions());
        if (cutter instanceof BasicTileCutter)
        {
            BasicTileCutter basicTileCutter = (BasicTileCutter)cutter;
            tileSpacing = basicTileCutter.getTileSpacing();
            tileMargin = basicTileCutter.getTileMargin();
            tilesPerRow = basicTileCutter.getTilesPerRow();
        }

        Image tile = cutter.getNextTile();
        while(tile != null)
        {
            Tile newTile = new Tile();
            newTile.setImage(addImage(tile));
            addNewTile(newTile);
            tile = cutter.getNextTile();
        }
    }

    private void refreshImportedTileBitmap()
            throws IOException
    {
        String imgFilename = tilebmpFile.getPath();

        Image image = ImageIO.read(new File(imgFilename));
        if (image == null)
        {
            throw new IOException("Failed to load " + tilebmpFile);
        }

        Toolkit tk = Toolkit.getDefaultToolkit();
        if (transparentColor != null)
        {
            int rgb = transparentColor.getRGB();
            image = tk.createImage(
                    new FilteredImageSource(image.getSource(),
                            new TransparentImageFilter(rgb)));
        }

        BufferedImage buffered = new BufferedImage(
                image.getWidth(null),
                image.getHeight(null),
                BufferedImage.TYPE_INT_ARGB);
        buffered.getGraphics().drawImage(image, 0, 0, null);

        refreshImportedTileBitmap(buffered);
    }

    private void refreshImportedTileBitmap(BufferedImage tilebmp)
    {
        assert tilebmp != null;

        tileCutter.reset();
        tileCutter.setImage(tilebmp);

        tileSetImage = tilebmp;
        tileDimensions = new Rectangle(tileCutter.getTileDimensions());

        int id = 0;
        Image tile = tileCutter.getNextTile();
        while(tile != null)
        {
            int imgId = getTile(id).tileImageId;
            overlayImage(imgId, tile);
            tile = tileCutter.getNextTile();
            id++;
        }

        fireTilesetChanged();
    }

    public void checkUpdate() throws IOException
    {
        if (tilebmpFile != null &&
                tilebmpFile.lastModified() > tilebmpFileLastModified)
        {
            refreshImportedTileBitmap();
            tilebmpFileLastModified = tilebmpFile.lastModified();
        }
    }

    public void setSource(String source)
    {
        String oldSource = externalSource;
        externalSource = source;

        fireSourceChanged(oldSource, source);
    }

    public void setBaseDir(String base)
    {
        this.base = base;
    }

    public void setTilesetImageFilename(String name)
    {
        if (name != null)
        {
            tilebmpFile = new File(name);
            tilebmpFileLastModified = tilebmpFile.lastModified();
        }
        else
        {
            tilebmpFile = null;
        }
    }

    public void setFirstGid(int firstGid)
    {
        this.firstGid = firstGid;
    }

    public void setName(String name)
    {
        String oldName = this.name;
        this.name = name;
        fireNameChanged(oldName, name);
    }

    public void setTransparentColor(Color color)
    {
        transparentColor = color;
    }

    public int addTile(Tile t)
    {
        if (t.getId() < 0)
        {
            t.setId(tiles.getMaxId() + 1);
        }

        if (tileDimensions.width < t.getWidth())
        {
            tileDimensions.width = t.getWidth();
        }

        if (tileDimensions.height < t.getHeight())
        {
            tileDimensions.height = t.getHeight();
        }

        t.getProperties().putAll(defaultTileProperties);

        tiles.put(t.getId(), t);
        t.setTileSet(this);

        fireTilesetChanged();

        return t.getId();
    }

    public void addNewTile(Tile t)
    {
        t.setId(-1);
        addTile(t);
    }

    public void removeTile(int i)
    {
        tiles.remove(i);
        fireTilesetChanged();
    }

    public int size()
    {
        return tiles.size();
    }

    public int getMaxTileId()
    {
        return tiles.getMaxId();
    }

    public Iterator iterator()
    {
        return tiles.iterator();
    }

    public Vector<Tile> generateGaplessVector()
    {
        Vector<Tile> gapless = new Vector<Tile>();

        for (int i = 0; i <= getMaxTileId(); i++)
        {
            if (getTile(i) != null)
                gapless.add(getTile(i));
        }

        return gapless;
    }

    public int getTileWidth()
    {
        return tileDimensions.width;
    }

    public int getTileHeight()
    {
        return tileDimensions.height;
    }

    public int getTileSpacing()
    {
        return tileSpacing;
    }

    public int getTileMargin()
    {
        return tileMargin;
    }

    public int getTilesPerRow()
    {
        return tilesPerRow;
    }

    public Tile getTile(int i)
    {
        try
        {
            return (Tile)tiles.get(i);
        }
        catch(ArrayIndexOutOfBoundsException a)
        {
        }
        return null;
    }

    public Tile getFirstTile()
    {
        Tile ret = null;
        int i = 0;
        while(ret == null && i <= getMaxTileId())
        {
            ret = getTile(i);
            i++;
        }
        return ret;
    }

    public String getSource()
    {
        return externalSource;
    }

    public String getBaseDir()
    {
        return base;
    }

    public String getTilebmpFile()
    {
        if (tilebmpFile != null)
        {
            try
            {
                return tilebmpFile.getCanonicalPath();
            }
            catch(IOException e)
            {
                
            }
        }

        return null;
    }

    public int getFirstGid()
    {
        return firstGid;
    }

    public String getName()
    {
        return name;
    }

    public Color getTransparentColor()
    {
        return transparentColor;
    }

    public String toString()
    {
        return getName() + " [" + size() + "]";
    }

    public int getTotalImages()
    {
        return images.size();
    }

    public Enumeration<String> getImagesIds()
    {
        Vector<String> v = new Vector();
        for (int id = 0; id <= images.getMaxId(); ++id)
        {
            if (images.containsId(id))
            {
                v.add(Integer.toString(id));
            }
        }
        return v.elements();
    }

    public int getIdByImage(Image i)
    {
        return images.indexOf(i);
    }

    public Image getImageById(int id)
    {
        return (Image)images.get(id);
    }

    public void overlayImage(int id, Image image)
    {
        images.put(id, image);
    }

    public Dimension getImageDimensions(int id)
    {
        Image img = (Image)images.get(id);
        if (image != null)
        {
            return new Dimension(img.getWidth(null), img.getHeight(null));
        }
        else
        {
            return new Dimension(0, 0);
        }
    }

    public int addImage(Image image)
    {
        return images.findOrAdd(image);
    }

    public int addImage(Image image, int id)
    {
        return images.put(id, image);
    }

    public void removeImage(int id)
    {
        images.remove(id);
    }

    public boolean isSetFromImage()
    {
        return tileSetImage != null;
    }

    public boolean isOneForOne()
    {
        Iterator itr = iterator();
        for (int id = 0; id <= images.getMaxId(); ++id)
        {
            int relations = 0;
            itr = iterator();

            while(itr.hasNext())
            {
                Tile t = (Tile)itr.next();
                if (t != null && t.getImageId() == id)
                {
                    relations++;
                }
            }
            if (relations != 1)
            {
                return false;
            }
        }

        return true;
    }

    public void setDefaultProperties(Properties defaultSetProperties)
    {
        defaultTileProperties = defaultSetProperties;
    }

    public void addTilesetChangeListener(TilesetChangeListener listener)
    {
        tilesetChangeListeners.add(listener);
    }

    public void removeTilesetChangeListener(TilesetChangeListener listener)
    {
        tilesetChangeListeners.remove(listener);
    }

    private void fireTilesetChanged()
    {
        TilesetChangedEvent event = new TilesetChangedEvent(this);
        for (TilesetChangeListener listener : tilesetChangeListeners)
        {
            listener.tilesetChanged(event);
        }
    }

    private void fireNameChanged(String oldName, String newName)
    {
        TilesetChangedEvent event = new TilesetChangedEvent(this);
        for (TilesetChangeListener listener : tilesetChangeListeners)
        {
            listener.nameChanged(event, oldName, newName);
        }
    }

    private void fireSourceChanged(String oldSource, String newSource)
    {
        TilesetChangedEvent event = new TilesetChangedEvent(this);
        for (TilesetChangeListener listener : tilesetChangeListeners)
        {
            listener.sourceChanged(event, oldSource, newSource);
        }
    }
}
